package org.fastframework.boot.context;

import org.fastframework.boot.annotation.*;
import org.fastframework.boot.data.jdbc.mysql.annotation.Mapper;
import org.fastframework.boot.data.jdbc.mysql.sql.SqlSession;
import org.fastframework.boot.proxy.CglibProxy;
import org.fastframework.boot.util.ClassUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ApplicationContext {

    public static ConcurrentHashMap<Class<?>, Object> beansFactory;
    public static List<Class<?>> pointcuts;
    //切点注解Class-代理Class
    public static ConcurrentHashMap<Class<?>, Class<?>> pointcutsMap;
//    public static List<MethodProxyInfo> methodProxyInfos;

    public ApplicationContext(String packageName) {
        init(packageName);
    }

    private void init(String packageName) {
        //初始化bean容器
        beansFactory = new ConcurrentHashMap<>();
        pointcuts = new ArrayList<>();
        pointcutsMap = new ConcurrentHashMap<>();
//        methodProxyInfos = new ArrayList<>();
        componentScan(packageName);
        componentInject();
    }

    /**
     * 组件扫描
     *
     * @param packageName
     */
    private void componentScan(String packageName) {
        List<Class<?>> classes = ClassUtil.getClasses(packageName);
        for (Class<?> clazz : classes) {
            if (clazz.isAnnotationPresent(Aspect.class)) {
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (!method.isAnnotationPresent(Pointcut.class)) continue;
                    Pointcut pointcut = method.getAnnotation(Pointcut.class);
                    String pointcutVal = pointcut.value();
                    try {
                        Class pointcutAnno = Class.forName(pointcutVal);
                        pointcutsMap.put(pointcutAnno, clazz);
                        pointcuts.add(pointcutAnno);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        for (Class<?> clazz : classes) {
            if (ClassUtil.hasAnno(clazz, Component.class)) {
                Class<?> tar = null;
                Method before = null;
                Method after = null;
                Method[] methods = clazz.getMethods();
                List<Method> tarMethods = new ArrayList<>();
                for (Method method : methods) {
                    Annotation[] methodAnnos = method.getAnnotations();
                    for (Annotation methodAnno : methodAnnos) {
                        if (pointcuts.contains(methodAnno.annotationType())) {
                            tarMethods.add(method);
                            tar = pointcutsMap.get(methodAnno.annotationType());
                            for (Method aspectMethod : tar.getMethods()) {
                                if (aspectMethod.isAnnotationPresent(Before.class)) {
                                    before = aspectMethod;
                                } else if (aspectMethod.isAnnotationPresent(After.class)) {
                                    after = aspectMethod;
                                }
                            }
                        }
                    }
                }
                if (clazz.isAnnotationPresent(Mapper.class)) {
                    beansFactory.put(clazz, SqlSession.getMapper(clazz));
                } else {
                    beansFactory.put(clazz, CglibProxy.initAop(ClassUtil.newInstance(clazz), ClassUtil.newInstance(tar), before, after, tarMethods));
                }
            }
        }
    }

    /**
     * 组件注入
     */
    private void componentInject() {
        try {
            for (Map.Entry<Class<?>, Object> entry : beansFactory.entrySet()) {
                Class<?> clazz = entry.getKey();
                Object obj = entry.getValue();
                Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(Inject.class)) {
                        Class<?> fieldClazz = field.getType();
                        field.setAccessible(true);
                        Object fieldObj = beansFactory.get(fieldClazz);
                        field.set(obj, fieldObj);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 返回bean
     *
     * @param clazz
     * @return
     */
    public Object getBean(Class<?> clazz) {
        return beansFactory.get(clazz);
    }

}
